#!/bin/bash
# /usr/local/bin/ban-qrouter

# OpenStack instances should not access physical network.

# priority: allow > block
# default is block

ALLOWED=(
#qrouter-00000000-0000-0000-0000-000000000000
)

BLOCKED=(
#qrouter-00000000-0000-0000-0000-000000000000
)

# physical/neutron_external network
PNET=(
#172.29.31.0/24
)

PORTS=(
#172.29.112.0/21,tcp,22
)

# set ALLOWED BLOCKED PNET in conf
ETCFILE="/usr/local/etc/ban-qrouter.conf"

find_router() {
    local router=$1
    local arr=$2
    local x=
    eval $(echo "for x in \${${arr}[@]}; do if [ \"\$x\" == \"\$router\" ]; then return 0;fi; done")
    return 1
}

f(){
    local cidr=$2
    local prot=$3 # maybe null
    local port=$4 # maybe null, e.g. "22", "1:1024"
    local cmdstr=

    if [ -n "$prot" ]; then
        [ -n "$port" ] || return 1
        cmdstr="-p $prot -m $prot -m multiport --dports $port"
    fi

    ip netns exec $_ns \
      iptables -$1 neutron-l3-agent-FORWARD -i $_dev -d $cidr \
        $cmdstr \
        -m conntrack --ctstate NEW -j DROP
}

clr() {
    local out=
    while true; do
        ip netns exec $_ns iptables -D neutron-l3-agent-FORWARD 1 &>/dev/null
        [ $? -eq 0 ] || break
    done
    out=$(ip netns exec $_ns iptables -n -L neutron-l3-agent-FORWARD 1)
    if [ -n "$out" ]; then
        echo "ERROR: failed to clear chain neutron-l3-agent-FORWARD in $_ns" >&1
        return 1
    fi
    echo "chain neutron-l3-agent-FORWARD in $_ns cleared."
}

prn() {
    echo "$_ns"
    ip netns exec $_ns iptables -n -L neutron-l3-agent-FORWARD
}

# Chain neutron-l3-agent-FORWARD (1 references)
# target     prot opt source               destination         
# DROP       tcp  --  0.0.0.0/0            172.16.112.0/21      tcp multiport dports 1:1024 ctstate NEW
# DROP       all  --  0.0.0.0/0            172.29.31.0/24       ctstate NEW
# DROP       all  --  0.0.0.0/0            172.29.112.0/24      ctstate NEW
# neutron-l3-agent-scope  all  --  0.0.0.0/0            0.0.0.0/0   
s() {
    local s_cidr=$1
    local s_prot=$2 # maybe null
    local s_port=$3 # maybe null
    local prot=
    local dest=
    local port=
    local line=

    [ -n "$s_prot" ] || s_prot=all

    IFS=$'\r\n'
    for line in $(ip netns exec $_ns iptables -n -L neutron-l3-agent-FORWARD |grep "^DROP"); do
        unset IFS
        read dumb prot dest port <<< $(echo $line |awk '{print $1" "$2" "$5" "$9}')
        if [[ "$s_cidr" == "$dest" && "$s_prot" == "$prot" && "$s_port" == "$port" ]]; then
            echo -e "$line"
            return 0
        fi
    done
    return 1
}

get_qr() {
    ip netns exec $_ns \
      ip addr show |grep "^[1-9]" |awk '{print $2}' |tr -d ':' |grep "^qr-" |head -n 1
}

allow() {
    local cidr=$1
    local prot=$2
    local port=$3

    while true;do
        s $cidr $prot $port || break
        echo "Delete Iptables rule: DROP $cidr $prot $port"
        f D $cidr $prot $port &>/dev/null
        if [ $? -ne 0 ]; then
            break
        fi
    done
}

block() {
    local cidr=$1
    local prot=$2
    local port=$3

    s $cidr $prot $port || { echo "Insert Iptables rule"; f I $cidr $prot $port; s $cidr $prot $port || echo "ERROR: block $_ns" >&1; }
}

PATH=/usr/sbin:/usr/local/bin:$PATH

[ -f $ETCFILE ] && . $ETCFILE

def="block"

if [ "$1" == "--help" ]; then
    echo -e "Ban OpenStack VM from the physical network"
    echo -e "Usage:"
    echo -e "\t $0 [--help|allowall|blockall]"
    echo -e "Parameters:"
    echo -e "\t--help        show this help."
    echo -e "\tallowall      delete DROP rules from all neutron router."
    echo -e "\tblockall      insert DROP rules into every neutron router."
    echo -e "\tclear         clear all rules on neutron-l3-agent-FORWARD chain."
    echo -e "\tprint         print rules on neutron-l3-agent-FORWARD chain."
    echo -e "Config File:"
    echo -e "\t$ETCFILE"
    echo -e "Config File Example:"
    echo -e "\t# priority: allow > block"
    echo -e "\t# default is block"
    echo -e "\t"
    echo -e "\tALLOWED=("
    echo -e "\t# admin"
    echo -e "\tqrouter-c0521c89-dddf-4d79-be25-81cdb790bfe4"
    echo -e "\t# admin"
    echo -e "\tqrouter-796e77fc-a8be-40ce-9bed-30bc4d6affbe"
    echo -e "\t# project1"
    echo -e "\tqrouter-3f3f8933-02e4-4ee6-b608-3c65e02e1a7a"
    echo -e "\t)"
    echo -e "\t"
    echo -e "\tBLOCKED=("
    echo -e "\tqrouter-664ed09a-b588-403a-aebf-11f25468a928"
    echo -e "\t)"
    echo -e "\t"
    echo -e "\t# physical/neutron_external network"
    echo -e "\tPNET=("
    echo -e "\t# Host network1 172.29.112.x and neutron physnet1 172.29.113.x 114.x 115.x"
    echo -e "\t172.29.112.0/21"
    echo -e "\t# Host network2"
    echo -e "\t172.29.31.0/24"
    echo -e "\t)"
    echo -e "\tPORTS=("
    echo -e "\t172.29.112.0/21,tcp,22"
    echo -e "\t)"
    echo -e "Crontab Example:"
    echo -e "\t*/15 * * * * /usr/local/bin/ban-qrouter"
    exit 0
elif [ "$1" == "allowall" ]; then
    BLOCKED=( )
    def="allow"
elif [ "$1" == "blockall" ]; then
    ALLOWED=( )
    def="block"
elif [ "$1" == "clear" ]; then
    def="clear"
elif [ "$1" == "print" ]; then
    def="print"
fi

# global: _ns _dev
for _ns in $(ip netns |grep "^qrouter-" |awk '{print $1}');do
    _dev=$(get_qr)
    [ -n "$_dev" ] || continue

    if [ "$def" == "clear" ]; then
        clr $_ns
        continue
    elif [ "$def" == "print" ]; then
        prn $_ns
        continue
    fi

    find_router $_ns ALLOWED
    if [ $? -eq 0 ]; then
        action="allow"
    else
    find_router $_ns BLOCKED
    if [ $? -eq 0 ]; then
        action="block"
    else
        action=$def
#        echo "WARNING: $_ns is NOT in any list." >&1
    fi
    fi

    echo "$action ${_ns}"

    for cidr in ${PNET[@]}; do
        $action $cidr
    done

    for ports in ${PORTS[@]}; do
        IFS=$','
        read cidr prot port <<< "${ports}"
        unset IFS
        $action $cidr $prot $port
    done
done

# crontab example: run every 15 minutes
# */15 * * * * /usr/local/bin/ban-qrouter

